package com.jksoft.meteo.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.jksoft.meteo.data.WeatherEntry;
import com.jksoft.meteo.data.WeatherForecast;
import com.jksoft.meteo.util.WeatherException;
import com.jksoft.meteo.util.WeatherNamespaceContext;
import com.jksoft.meteo.util.WindDirection;

public class WeatherReader extends AbstractWeatherReader {
	private static final String DATA_ATTRIBUTE_NAME = "data";
	private static final String FORECAST_XPATH = "//forecast_conditions";
	private static final String TEMP_AND_SPEED_REGEX = "\\d{1,2}";
	private static final String WEATHER_URL = "http://www.google.com/ig/api?weather=";
	private static final String[] XPATHS = { "//humidity[@data]",
			"//temp_c[@data]", "//condition[@data]", "//wind_condition[@data]",
			"//icon[@data]" };

	private Document doc;

	public WeatherReader(String location) {
		super(location);
	}

	private String executeXPath(Document doc, String xpath, String attr)
			throws XPathExpressionException {
		String data = null;

		NodeList nl = executeXPath(doc, xpath);

		if (nl != null && nl.item(0) != null)
			data = nl.item(0).getAttributes().getNamedItem(attr).getNodeValue();

		return data;
	}

	public void process() throws WeatherException {
		try {
			String weatherData = readUrl(new URL(WEATHER_URL + location));

			doc = documentFromString(weatherData, true);

			wf = new WeatherForecast();

			String humidity = executeXPath(doc, XPATHS[0], DATA_ATTRIBUTE_NAME);
			String wind = executeXPath(doc, XPATHS[3], DATA_ATTRIBUTE_NAME)
					.split(" ")[1];
			String iconURL = "http://www.google.com"
					+ executeXPath(doc, XPATHS[4], DATA_ATTRIBUTE_NAME);

			wf.setHumidity(getAllMatches(humidity, TEMP_AND_SPEED_REGEX).get(0));
			wf.setTemperature(executeXPath(doc, XPATHS[1], DATA_ATTRIBUTE_NAME));
			wf.setCondition(executeXPath(doc, XPATHS[2], DATA_ATTRIBUTE_NAME));
			wf.setWind(WindDirection.getDirection(wind));
			wf.setIconURL(iconURL);

			processForecast();
		} catch (Exception e) {
			throw new WeatherException(e.getMessage(), e.getCause());
		}
	}

	private void processForecast() throws WeatherException {
		try {
			NodeList children = null;
			NodeList nl = executeXPath(doc, FORECAST_XPATH);

			for (int i = 0; i < nl.getLength(); i++) {
				children = nl.item(i).getChildNodes();

				String condition = children.item(4).getAttributes()
						.getNamedItem(DATA_ATTRIBUTE_NAME).getNodeValue();
				String day = children.item(0).getAttributes()
						.getNamedItem(DATA_ATTRIBUTE_NAME).getNodeValue();
				String high = children.item(2).getAttributes()
						.getNamedItem(DATA_ATTRIBUTE_NAME).getNodeValue();
				String iconURL = "http://google.com"
						+ children.item(3).getAttributes()
								.getNamedItem(DATA_ATTRIBUTE_NAME)
								.getNodeValue();
				String low = children.item(1).getAttributes()
						.getNamedItem(DATA_ATTRIBUTE_NAME).getNodeValue();

				WeatherEntry entry = new WeatherEntry();

				entry.setCondition(condition);
				entry.setDay(day);
				entry.setHighTemperature(fahrenheitToCelsius(high));
				entry.setIcon(iconURL);
				entry.setLowTemperature(fahrenheitToCelsius(low));

				wf.addForecastEntry(entry);
			}
		} catch (Exception e) {
			throw new WeatherException(e.getMessage(), e.getCause());
		}
	}



	private static Document documentFromString(String xmlContent,
			boolean namespaceAware) throws ParserConfigurationException,
			SAXException, IOException {
		Document doc = null;

		doc = documentFromString(new InputSource(new StringReader(xmlContent)),
				namespaceAware);

		return doc;
	}

	private static Document documentFromString(InputSource is,
			boolean namespaceAware) throws ParserConfigurationException,
			SAXException, IOException {
		Document doc = null;

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(namespaceAware);
		DocumentBuilder builder = dbf.newDocumentBuilder();
		doc = builder.parse(is);

		return doc;
	}

	private static NodeList executeXPath(Document doc, String xpathString)
			throws XPathExpressionException {
		Object result = null;

		XPath xpath = XPathFactory.newInstance().newXPath();
		xpath.setNamespaceContext(getNamespaceContext());
		XPathExpression expr = xpath.compile(xpathString);
		result = expr.evaluate(doc, XPathConstants.NODESET);

		return (NodeList) result;
	}

	private static String readUrl(URL url) throws MalformedURLException,
			IOException {
		StringBuffer sb = new StringBuffer();

		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		conn.setRequestMethod("GET");

		BufferedReader br = new BufferedReader(new InputStreamReader(
				conn.getInputStream()));

		String line = null;

		while ((line = br.readLine()) != null)
			sb.append(line);

		return sb.toString();
	}

	// Data conversion
	private static String fahrenheitToCelsius(int tempInFahrenheit) {
		return new Integer((tempInFahrenheit - 32) * 5 / 9).toString();
	}

	private static String fahrenheitToCelsius(String tempInFahrenheit) {
		return fahrenheitToCelsius(Integer.parseInt(tempInFahrenheit));
	}

	private static List<String> getAllMatches(String input, String regex) {

		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(input);

		List<String> matches = new ArrayList<String>();

		while (matcher.find())
			matches.add(matcher.group());

		return matches;
	}

	private static NamespaceContext getNamespaceContext() {
		Map<String, String> prefixes = new HashMap<String, String>();

		prefixes.put("yahoo", "http://www.yahooapis.com/v1/base.rng");
		prefixes.put("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0");
		prefixes.put("geo", "http://www.w3.org/2003/01/geo/wgs84_pos#");
		prefixes.put("yahooDefault", "http://where.yahooapis.com/v1/schema.rng");

		return new WeatherNamespaceContext(prefixes);
	}
}
